package com.guoyan.blockchain.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.guoyan.blockchain.common.client.ChannelClient;
import com.guoyan.blockchain.common.exception.FabricException;
import com.guoyan.blockchain.component.model.CalModel;
import com.guoyan.blockchain.component.model.Model;
import com.guoyan.blockchain.component.model.TxModel;
import com.guoyan.blockchain.common.response.BaseResponse;
import com.guoyan.blockchain.service.IMainService;
import com.guoyan.blockchain.service.parent.ParentService;
import com.guoyan.blockchain.common.util.EncodeUtil;
import org.apache.commons.codec.binary.Hex;
import org.hyperledger.fabric.sdk.*;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author yeym
 */
@Service
public class MainServiceImpl extends ParentService implements IMainService {
    @Override
    public BaseResponse save(JSONObject jsonObject,Integer type) {
        Model model = null;
        if (type==0) {
            model = new TxModel();
        } else {
            model = new CalModel();
        }
        model = model.fromJson(jsonObject);
        if (!model.verify()) {
            System.out.println(JSONObject.toJSONString(model));
            return BaseResponse.baseError(BaseResponse.dataInvalid,model);
        }
        try {
            Channel channel = fabricClient.getInstance().getChannel("mychannel");
            if (channel == null) {
                channel = fabricClient.getInstance().deSerializeChannel(new File("channels/mychannel.ser"));
                channel.initialize();
            }
            ChannelClient channelClient = new ChannelClient("mychannel", channel, fabricClient);
            TransactionProposalRequest request = fabricClient.getInstance().newTransactionProposalRequest();
            ChaincodeID ccid = ChaincodeID.newBuilder().setName("fabcar").build();
            request.setChaincodeID(ccid);
            request.setFcn("save");
            String[] arguments = {EncodeUtil.sha256(model), JSONObject.toJSONString(model)};
            request.setArgs(arguments);
            request.setProposalWaitTime(100000);
            Collection<ProposalResponse> responses = channelClient.sendTransactionProposal(request);
            String txId = "";
            boolean flag = true;
            for (ProposalResponse res : responses) {
                ChaincodeResponse.Status status = res.getStatus();
                if (flag) {
                    txId = res.getTransactionID();
                    flag = false;
                }
            }
            CompletableFuture<BlockEvent.TransactionEvent> cf = channelClient.sendTransaction(responses);
            return BaseResponse.successful(txId);
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResponse.error;
        }
    }

    @Override
    public BaseResponse saveAny(String key, String value) {
        try {
            Channel channel = fabricClient.getInstance().getChannel("mychannel");
            if (channel == null) {
                channel = fabricClient.getInstance().deSerializeChannel(new File("channels/mychannel.ser"));
                channel.initialize();
            }
            ChannelClient channelClient = new ChannelClient("mychannel", channel, fabricClient);
            TransactionProposalRequest request = fabricClient.getInstance().newTransactionProposalRequest();
            ChaincodeID ccid = ChaincodeID.newBuilder().setName("fabcar").build();
            request.setChaincodeID(ccid);
            request.setFcn("save");
            String[] arguments = {key, value};
            request.setArgs(arguments);
            request.setProposalWaitTime(100000);
            Collection<ProposalResponse> responses = channelClient.sendTransactionProposal(request);
            String txId = "";
            boolean flag = true;
            for (ProposalResponse res : responses) {
                ChaincodeResponse.Status status = res.getStatus();
                if (flag) {
                    txId = res.getTransactionID();
                    flag = false;
                }
            }
            CompletableFuture<BlockEvent.TransactionEvent> cf = channelClient.sendTransaction(responses);
            return BaseResponse.successful(txId);
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResponse.error;
        }
    }

    @Override
    public BaseResponse queryHis(String userName) {
        try {
            Channel channel = fabricClient.getInstance().getChannel("mychannel");
            if (channel == null) {
                channel = fabricClient.getInstance().deSerializeChannel(new File("channels/mychannel.ser"));
                channel.initialize();
            }
            ChannelClient channelClient = new ChannelClient("mychannel", channel, fabricClient);
            TransactionProposalRequest request = fabricClient.getInstance().newTransactionProposalRequest();
            ChaincodeID ccid = ChaincodeID.newBuilder().setName("fabcar").build();
            request.setChaincodeID(ccid);
            request.setFcn("queryHis");
            System.out.println(userName);
            String[] arguments = {userName};
            request.setArgs(arguments);
            request.setProposalWaitTime(100000);
            Collection<ProposalResponse> responses = channelClient.sendTransactionProposal(request);
            String value = "";
            boolean flag = true;
            for (ProposalResponse res : responses) {
                ChaincodeResponse.Status status = res.getStatus();
                if (flag) {
                    value = res.getMessage();
                    flag = false;
                }
            }
            CompletableFuture<BlockEvent.TransactionEvent> cf = channelClient.sendTransaction(responses);
            return BaseResponse.successful(value);
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResponse.error;
        }
    }

    @Override
    public BaseResponse saveList(JSONArray jsonArray, Integer type) {
        Map<String,String> re = new HashMap<>();
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            String key = jsonObject.getString("key");
            jsonObject.remove("key");
            Object data = this.save(jsonObject,type).getData();
            re.put(key,data == null ? null : data.toString());
        }
        re.forEach((x,y)-> System.out.println(x+"=====>"+y));
        return BaseResponse.successful(re);
    }

    @Override
    public BaseResponse query(String txId) {
        Channel channel = null;
        try {
            channel = fabricClient.getInstance().getChannel("mychannel");
            if (channel == null) {
                channel = fabricClient.getInstance().deSerializeChannel(new File("channels/mychannel.ser"));
                channel.initialize();
            }
            BlockInfo blockInfo = channel.queryBlockByTransactionID(txId);
            AtomicReference<String> value = new AtomicReference<>("");
            AtomicReference<Date> time = new AtomicReference<>(new Date());
            blockInfo.getEnvelopeInfos().forEach(x -> {
                if (x.getTransactionID().equals(txId)) {
                    BlockInfo.TransactionEnvelopeInfo tx = (BlockInfo.TransactionEnvelopeInfo) x;
                    time.set(tx.getTimestamp());
                    tx.getTransactionActionInfos().forEach(y -> y.getTxReadWriteSet().getNsRwsetInfos().forEach(z -> {
                        try {
                            if (!"lscc".equals(z.getNamespace())) {
                                z.getRwset().getWritesList().forEach(p -> value.set(p.getValue().toStringUtf8()));
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }));
                }
            });
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("putData",JSONObject.parseObject(value.get(),Map.class));
            jsonObject.put("putTime",time.get());
            jsonObject.put("blockHeight",blockInfo.getBlockNumber());
            return BaseResponse.successful(jsonObject);
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResponse.error("TxId is not exist");
        }
    }

    @Override
    public BaseResponse getAll(String channelName, Integer count, String startHash) {
        JSONObject jsonObject = new JSONObject();
        try {
            Channel channel = fabricClient.getInstance().getChannel(channelName);
            if (channel == null) {
                channel = fabricClient.getInstance().deSerializeChannel(new File("channels/" + channelName + ".ser"));
                channel.initialize();
            }
//            if (startHash == null || startHash.equals(""))
            BlockchainInfo blockchainInfo = channel.queryBlockchainInfo();
            jsonObject.put("blockHeight", blockchainInfo.getHeight());
            jsonObject.put("currentHash", Hex.encodeHexString(blockchainInfo.getCurrentBlockHash()));
            jsonObject.put("preHash", Hex.encodeHexString(blockchainInfo.getPreviousBlockHash()));
            BlockInfo blockInfo = null;
            JSONObject blocking = null;
            long n = blockchainInfo.getHeight();
//            for (long i = n-1; i >= (n > 10 ? n-10 : 0); i--) {
//                blocking = new JSONObject();
//                blockInfo = channel.queryBlockByNumber(i);
//                blocking.put("txNum", blockInfo.getTransactionCount());
//                jsonObject.put("blockInfo" + i, blocking);
//            }
            for (int i = 0; i < n; i++) {
                blocking = new JSONObject();
                blockInfo = channel.queryBlockByNumber(i);
                blocking.put("txNum", blockInfo.getTransactionCount());
                jsonObject.put("blockInfo" + i, blocking);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return BaseResponse.successful(jsonObject);
    }
}
